package impl

import (
	"context"
	"encoding/base64"
	"fmt"
	"net/http"
	"time"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/hertz-contrib/paseto"
	"golang.org/x/crypto/bcrypt"
	"golang.org/x/crypto/scrypt"
	"hertz-nbdemo/biz/dal/model"
	"hertz-nbdemo/biz/dal/query"
	"hertz-nbdemo/biz/dto"
	"hertz-nbdemo/biz/service"
	"hertz-nbdemo/pkg/logger"
	"hertz-nbdemo/pkg/redis"
	"hertz-nbdemo/util/common"
)

var _ service.PasetoService = &PasetoServiceImpl{}

type PasetoServiceImpl struct {
	BaseService
	genTokenFunc paseto.GenTokenFunc
}

func NewPasetoService() service.PasetoService {
	return &PasetoServiceImpl{
		genTokenFunc: paseto.DefaultGenTokenFunc(),
	}
}

func (p *PasetoServiceImpl) genToken(uid int64) (string, error) {
	now := time.Now()
	return p.genTokenFunc(&paseto.StandardClaims{
		Issuer:    "default-issuer",
		ExpiredAt: now.Add(time.Hour),
		NotBefore: now,
		IssuedAt:  now,
	}, utils.H{
		"uid": uid,
	}, nil)
}

func genSalt(password string) ([]byte, error) {
	bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
	return bytes, err
}

// ScryptPwd 加密
func ScryptPwd(password, salt []byte) (string, error) {
	const PwdHashByte = 10
	key, err := scrypt.Key(password, salt, 16384, 8, 1, PwdHashByte)
	if err != nil {
		return "", err
	}
	FinPwd := base64.StdEncoding.EncodeToString(key)
	return FinPwd, nil
}

func (p *PasetoServiceImpl) Login(ctx context.Context, c *app.RequestContext) {
	loginDto := new(dto.LoginDto)
	err := c.Bind(loginDto)
	if err != nil {
		p.BadRequestResponse(c, err.Error())
		return
	}
	tu := query.Q.TUser
	first, _ := tu.WithContext(ctx).Where(tu.Username.Eq(loginDto.Username)).Select(tu.ALL).First()
	if first == nil {
		p.Response(c, common.Failed("用户不存在"))
		return
	}
	if pwd, _ := ScryptPwd([]byte(loginDto.Password), []byte(first.PasswordSalt)); pwd != first.Password {
		p.Response(c, common.Failed("用户密码错误"))
		return
	}
	token, err := p.genToken(first.ID)
	if err != nil {
		logger.Logger.Error(fmt.Sprintf("生成密码哈希失败： %v", err.Error()))
		c.JSON(http.StatusOK, common.Failed(err.Error()))
		return
	}

	redis.RDb.Set(ctx, fmt.Sprintf("u-sub:%d", first.ID), "alice", time.Hour)
	c.JSON(http.StatusOK, common.Success(token))
}

func (p *PasetoServiceImpl) Register(ctx context.Context, c *app.RequestContext) {
	loginDto := new(dto.LoginDto)
	err := c.Bind(loginDto)
	if err != nil {
		p.BadRequestResponse(c, err.Error())
		return
	}
	tu := query.Q.TUser
	first, _ := tu.WithContext(ctx).Where(tu.Username.Eq(loginDto.Username)).Select(tu.ALL).First()
	if first != nil {
		p.Response(c, common.Failed("用户已存在"))
		return
	}

	salt, err := genSalt(loginDto.Password)
	if err != nil {
		logger.Logger.Error(fmt.Sprintf("生成密码哈希失败： %v", err.Error()))
		c.JSON(http.StatusOK, common.Failed(err.Error()))
		return
	}
	password, err := ScryptPwd([]byte(loginDto.Password), salt)
	if err != nil {
		logger.Logger.Error(fmt.Sprintf("生成密码哈希失败： %v", err.Error()))
		c.JSON(http.StatusOK, common.Failed(err.Error()))
		return
	}
	err = tu.WithContext(ctx).Create(&model.TUser{
		Username:     loginDto.Username,
		Password:     password,
		PasswordSalt: string(salt),
	})
	if err != nil {
		p.Response(c, common.Failed(err.Error()))
		return
	}
	first, _ = tu.WithContext(ctx).Where(tu.Username.Eq(loginDto.Username)).Select(tu.ALL).First()
	token, err := p.genToken(first.ID)
	if err != nil {
		p.Response(c, err.Error())
		return
	}

	redis.RDb.Set(ctx, fmt.Sprintf("u-sub:%d", first.ID), "alice", time.Hour)
	c.JSON(http.StatusOK, common.Success(token))
}

func (p *PasetoServiceImpl) TestToken(ctx context.Context, c *app.RequestContext) {
	c.JSON(http.StatusOK, common.SimpleSuccess())
}
